package top.mcwebsite.playandroid.lib.task

import android.os.Looper
import java.util.concurrent.Executors
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext

class DefaultTaskDispatcher : TaskDispatcher {

    private val scope = CoroutineScope(EmptyCoroutineContext)

    private companion object {
        val LowPriority = Executors.newFixedThreadPool(1) { r ->
            val thread = Thread(r)
            thread.priority = Thread.MIN_PRIORITY
            thread
        }.asCoroutineDispatcher()
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    override fun dispatch(taskChain: TaskChain): TaskChainController {
        val job = scope.launch(start = CoroutineStart.LAZY, context = Dispatchers.Unconfined) {
            val taskContext: TaskContext = TaskContext()
            if (taskChain.initParams != null) {
                taskContext.params.plus(taskChain.initParams!!)
            }
            while (!taskChain.taskQueue.isEmpty()) {
                taskChain.taskQueue.poll()?.run(taskChain, taskContext)
            }
        }
        return DefaultTaskChainController(job)
    }

    private suspend fun TaskNode.run(taskChain: TaskChain, taskContext: TaskContext) {
        runOnDispatch(this.task.name, this.task.dispatchMode) {
            this.state = TaskState.RUNNING
            try {
                val res = this.task.job.invoke(taskContext)
                taskContext.params[this.task.name] = res
                this.state = TaskState.FINISH
            } catch (t: Throwable) {
                // TODO
                this.state = TaskState.ERROR
            }
            // 检查 next 是否满足运行条件
            while (!this.nextTasks.isNullOrEmpty()) {
                val taskNode = this.nextTasks!!.poll()
                if (taskNode != null && taskNode.task.dependsOn!!.all {
                        taskChain.tasks[it] == null || (taskChain.tasks[it]!!.state == TaskState.FINISH
                                || taskChain.tasks[it]!!.state == TaskState.ERROR)
                    }) {
                    taskNode.run(taskChain, taskContext)
                }
            }
        }
    }

    @OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
    private fun runOnDispatch(taskName: String, mode: DispatchMode, block: suspend () -> Unit) {
        val dispatcher = when (mode) {
            DispatchMode.MAIN -> if (Looper.myLooper() == Looper.getMainLooper()) Dispatchers.Unconfined else Dispatchers.Main
            DispatchMode.IO -> Dispatchers.IO
            DispatchMode.DEFAULT -> Dispatchers.Default
            DispatchMode.NEW_THREAD -> newSingleThreadContext("work-$taskName")
            DispatchMode.LOW_PRIORITY_THREAD -> LowPriority
        }
        scope.launch(dispatcher) { block() }
    }
}

class DefaultTaskChainController(
    private var parentJob: kotlinx.coroutines.Job
) : TaskChainController {

    override fun start() {
        parentJob.start()
    }

    override fun cancel() {
        parentJob.cancel()
    }

}